/**
 * File: GraphWriter.java
 * Created by: mhaimel
 * Created on: Mar 8, 2010
 * CVS:  $Id: GraphWriter.java 1.0 Mar 8, 2010 3:44:36 PM mhaimel Exp $
 */
package uk.ac.ebi.curtain.model.graph.io;

import static uk.ac.ebi.curtain.model.graph.io.GraphUtil.ARC_START;
import static uk.ac.ebi.curtain.model.graph.io.GraphUtil.GRAPH_START;
import static uk.ac.ebi.curtain.model.graph.io.GraphUtil.HEADER_VALUE;
import static uk.ac.ebi.curtain.model.graph.io.GraphUtil.NODE_START;
import static uk.ac.ebi.curtain.model.graph.io.GraphUtil.SEP_VALUE;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

import uk.ac.ebi.curtain.model.graph.Arc;
import uk.ac.ebi.curtain.model.graph.Graph;
import uk.ac.ebi.curtain.model.graph.Node;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;

/**
 * @author mhaimel
 *
 */
public class GraphWriter<N,A> {

	private static final Object[] EMPTY_ARR = new Object[0];
	private final Graph<N,A> graph;
	@SuppressWarnings("unchecked")
	private final WriteMapper<Graph> graphWriter;
	private final WriteMapper<Node<N, A>> nodeWriter;
	private final WriteMapper<Arc<N, A>> arcWriter;
	private volatile WriteMapper<N> nodeValueMapper;
	private volatile WriteMapper<A> arcValueMapper;
	private final AtomicBoolean writeNodes = new AtomicBoolean(true);
	private final AtomicBoolean writeArc = new AtomicBoolean(true);
	
	public GraphWriter(Graph<N, A> graph) {
		this.graph = graph;
		this.nodeValueMapper = null;
		this.arcValueMapper = null;
		this.graphWriter = new GraphMapper();
		this.nodeWriter = new NodeMapper<N,A>();
		this.arcWriter = new ArcMapper<N, A>();
	}
	
	private GraphWriter<N, A> setWriteNodeValues(boolean writeNodes) {
		this.writeNodes.set(writeNodes);
		return this;
	}	
	
	private GraphWriter<N, A> setWriteArcValues(boolean writeArc) {
		this.writeArc.set(writeArc);
		return this;
	}
	
	private boolean getWriteNodeValues() {
		return this.writeNodes.get();
	}
	private boolean getWriteArcValues() {
		return this.writeArc.get();
	}
	
	public GraphWriter<N, A> setNodeValueMapper(WriteMapper<N> nodeMapper) {
		this.nodeValueMapper = nodeMapper;
		return this;
	}
	
	public GraphWriter<N, A> setArcValueMapper(WriteMapper<A> arcMapper) {
		this.arcValueMapper = arcMapper;
		return this;
	}
	
	/**
	 * uses {@link #write(File, boolean)} with default append is FALSE
	 * @param file
	 * @throws IOException 
	 */
	public void write(File file) throws IOException{
		write(file,false);
	}

	/**
	 * 
	 * @param file
	 * @param append
	 * @throws IOException 
	 */
	public void write(File file, boolean append) throws IOException{
		PrintStream out = null;
		try{
			out = new FileIO(file,append).getPrintStream();
			write(out);
		}finally{
			FileIO.closeQuietly(out);
		}
	}
	
	/**
	 * 
	 * @param out
	 * @throws IOException 
	 */
	public void write(PrintStream out) throws IOException {
		// comment
		Graph<N,A> g = getGraph();
		boolean writeNodes = getWriteNodeValues();
		boolean writeArcs = getWriteArcValues();
		//write Graph details
		out.println(getGraphHeader());
		out.println(writeGraphLine(g));		
		if(writeNodes){
			out.println(getNodeHeader());
			// write nodes
			Long n = g.getNodeSize();
			for(Long i = 0l; i < n; ++i){
				Node<N,A> node = g.getNode(i);
				out.println(writeNode(node));
			}
		}
		
		// write Arcs
		if(writeArcs){
			out.println(getArcHeader());
			Long a = g.getArcCount();
			for(Long i = 0l; i < a; ++i){
				Arc<N, A> arc = g.getArc(i);
				out.println(writeArc(arc));
			}
		}
	}

	private String writeNode(Node<N, A> node) {
		Object[] arr1 = nodeWriter.toArray(node);
		Object[] arr2 = EMPTY_ARR;
		if(null != nodeValueMapper){
			arr2 = nodeValueMapper.toArray(node.getValue());
		} 
		return NODE_START+ join(arr1, arr2, SEP_VALUE);
	}
	
	private String writeArc(Arc<N, A> arc) {
		Object[] arr1 = arcWriter.toArray(arc);
		Object[] arr2 = EMPTY_ARR;
		if(null != arcValueMapper){
			arr2 = arcValueMapper.toArray(arc.getValue());
		} 
		return ARC_START+ join(arr1, arr2, SEP_VALUE);
	}

	private String writeGraphLine(Graph<N, A> g) {
		Object[] array = graphWriter.toArray(g);
		String str = StringUtils.join(array,'\t');
		return GRAPH_START + str;
	}


	private Graph<N, A> getGraph() {
		return this.graph;
	}

	private String getGraphHeader() {
		return HEADER_VALUE+StringUtils.join(graphWriter.getHeader(),'\t');
	}

	private String getNodeHeader() {
		return HEADER_VALUE+join(nodeWriter.getHeader(),getHeader(nodeValueMapper),SEP_VALUE);
	}
	
	private String getArcHeader() {
		return HEADER_VALUE+join(arcWriter.getHeader(),getHeader(this.arcValueMapper),SEP_VALUE);
	}

	private Object[] getHeader(WriteMapper<?> mapper) {
		if(null == mapper){
			return EMPTY_ARR;
		} else {
			return mapper.getHeader();
		}
	}

	private String join(Object[] arr1, Object[] arr2, char sep) {
		Object[] joined = ArrayUtils.addAll(arr1, arr2);
		return StringUtils.join(joined,sep);
	}
	
//	private static class DefaultWriteMapper<T> implements WriteMapper<T>{
//		@Override
//		public String[] getHeader() {
//			return new String[0];
//		}
//		@Override
//		public Object[] toArray(T value) {
//			return EMPTY_ARR;
//		}
//	}
	
}
